import multiprocessing
import random
import sys
import threading
import time
from socket import *
from multiprocessing import Queue
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal, QEventLoop, QTimer
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QDialog, QApplication
# from spoolingui import Ui_spoolingui

# 本地回环流量地址，作为进程间通信的方式
HOST = '127.0.0.1'
PORT = 9999  # 服务端端口号
ADDR = (HOST, PORT)
BUFFSIZE = 1024  # 数据的缓存区大小

maxPoolLength = 400  # 输出井容量
maxReqBlockNum = 40  # 请求块数容量

usedPoolLength = 0#已使用井长度
location = 0  # 输出井缓存目录tmp下的文件名,文件名同时还附上了用户名，递增1并且求模，以限制在0~40
speed=5#调节调度速度，1-10档

need=[0,10,10,20]#need[0]个无意义。用户1，2和spooling需要打印的文件个数
printed=[0,0,0,0]#第0个无意义。用户1，2和spooling已经打印的文件个数

class pcbobj():
    id = 0,  #进程标识符0服务，用户1、2，输出3
    name = 'user'#进程名
    status = 0  # 0可执行，等待1、2、3，结束4
    length = 0#本次要打印的长度
    buf = []#临时存放本次打印的msg

class reqblock():
    name = 'user'#请求打印的进程名
    filename = 'filename'#在输出井中的文件名
    length = 0#打印的长度

def serverThreading(PCB,queue):
    while True:
        s = socket(AF_INET, SOCK_STREAM)
        s.bind(ADDR)
        s.listen(1000)  # 最大连接数
        sock, addr = s.accept()

        id_name_msg = sock.recv(BUFFSIZE).decode('utf-8')
        splited_list = id_name_msg.split('_')  # 字符串分割
        id = int(splited_list[0])
        name = splited_list[1]
        msg = splited_list[2]
        '''print('服务程序收到' + name + '的信息：' + msg)'''
        global usedPoolLength
        global location
        global printed
        global maxPoolLength
        if name == 'spooling':  # 如果是pooling进程发来的消息
            if msg == 'WAITING':  # ③SP00LING进程在进行输出时，若输出井空，则进入“等待状态2”
                print('                                                                                                                                            spooling进入“等待状态2”')
                PCB[3].status = 2
            elif msg == 'PRINTED':  # ④SP00LING进程输出一个信息块后，应立即释放该信息块所占的输出井空间，并将正在等待输出的进程置为“可执行状态”。
                print('                                                                                                                                            spooling打印了：' + splited_list[3])
                length=len(splited_list[3])
                usedPoolLength -= length  # 释放该信息块所占的输出井空间
                printed[3]+=1
                print('释放输出井空间'+str(length)+'个，当前剩余'+str(maxPoolLength-usedPoolLength))
                if PCB[1].status == 1 or PCB[1].status == 3:
                    PCB[1].status = 0
                    print('                                                                                                                                            userA进程置为“可执行状态0”成功')
                if PCB[2].status == 1 or PCB[2].status == 3:
                    PCB[2].status = 0
                    print('                                                                                                                                            userB进程置为“可执行状态0”成功')
                if printed[3]==need[3]:
                    PCB[3].status = 4
            else:
                print('不能识别spooling发来的消息：' + msg)
        elif name == 'userA' or name == 'userB':  # 如果是用户进程发来的相应处理

            length = len(msg)
            buf = list(msg)  # python的字符串和列表是两种东西，字符串不能append()所以需要转换...
            print('                                                       用户'+name+'请求输出：'+msg)
            if usedPoolLength + length > maxPoolLength:  # ②服务程序在将输出信息送输出井时，如发现输出井已满，将调用进程置为“等待状态1”
                print('                                                       用户' + name + '请求失败！井空间不足！')
                PCB[id].status = 1
                PCB[id].length =length
                PCB[id].buf = buf
                '''sock.send('POOLFULL'.encode('utf-8'))  # 发送井满提示'''
            elif queue.full():  # ⑥当用户进程申请请求输出块时，若没有可用请求块时，调用进程进人“等待状态3”。
                print('                                                       用户' + name + '请求失败！没有请求块！')
                PCB[id].status = 3
                PCB[id].length =length
                PCB[id].buf = buf
                '''sock.send('REQBLOCKFULL'.encode('utf-8'))  # 发送请求块满提示'''
            else:
                path = './tmp'
                if not os.path.exists(path):
                    os.makedirs(path)
                filename = str(location) + '_'+name  # 以0,1..作为输出井缓存文件名
                f = open('tmp//' + filename + '.txt', 'w+')
                f.write(msg)
                f.close()
                print('                                                       用户' + name + '请求输出的信息已进入输出井')
                new_req=reqblock()
                new_req.name=name
                new_req.filename = filename
                new_req.length=length
                queue.put(new_req)
                usedPoolLength += length

                location = (location + 1) % maxReqBlockNum
                PCB[id].length = 0
                PCB[id].buf = []
                #ffffffffffffffffffffffffffffffffffffffflag
                print('请求块已进入队列排队...当前队列长度'+str(queue.qsize()))
                printed[id]+=1
                '''sock.send('SUCCESS'.encode('utf-8'))  # 发送成功提示'''
                if printed[id]>=need[id]:
                    print('                                                       用户' + name + '进程已结束')#①进程执行完成时，置为“结束”态。
                    PCB[id].status = 4

                if PCB[3].status == 2:  # ⑤服务程序在输出信息到输出井并形成输出请求信息块后，若SP00LING进程处于等待态，则将其置为“可执行状态”。
                    PCB[3].status = 0
                    print('SP00LING进程切换为“可执行状态”成功！')
        else:
            print('服务进程不能识别该不合法的消息：' + id_name_msg)

def userA(myPCB):
    # 根据PCB恢复断点现场
    id = myPCB.id
    name = myPCB.name
    status = myPCB.status
    length = myPCB.length
    buf = myPCB.buf

    if status == 0:
        tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 创建socket对象
        tcpCliSock.connect(ADDR)
        if length == 0:  # 上次的输出如果进了输出井，就随机生成要打印的信息
            while True:
                j = random.randint(0, 9)
                if (j == 0) and (length != 0):  # 以0结束此次输出
                    break
                buf.append(str(j))
                length += 1
        msg = "".join(buf)  # list转为str字符串
        id_name_msg = str(id) + '_' + name + '_' + msg
        tcpCliSock.send(id_name_msg.encode('utf-8'))  # 发送消息
        '''while True:
            recv_msg = tcpCliSock.recv(BUFFSIZE)  # 读取消息
            msg = recv_msg.decode('utf-8')
            if msg == 'POOLFULL':  # 失败1，输出井满
                print(name + '进程进入等待状态1')
            elif msg == 'REQBLOCKFULL':  # 失败3，请求块满
                print(name + '进程进入等待状态3')
            elif msg == 'SUCCESS':  # 成功打印了（假的，实际上只是进了输出井）
                print(name + '打印成功了（假打印）')
            else:
                print('不能识别的消息：' + msg)
            break'''
        tcpCliSock.close()

def userB(myPCB):
    # 根据PCB恢复断点现场
    id = myPCB.id
    name = myPCB.name
    status = myPCB.status
    length = myPCB.length
    buf = myPCB.buf

    if status == 0:
        tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 创建socket对象
        tcpCliSock.connect(ADDR)
        if length == 0:  # 上次的输出如果进了输出井，就随机生成要打印的信息
            while True:
                j = random.randint(0, 9)
                if (j == 0) and (length != 0):  # 以0结束此次输出
                    break
                buf.append(str(j))
                length += 1
        msg = "".join(buf)  # list转为str字符串
        id_name_msg = str(id) + '_' + name + '_' + msg
        tcpCliSock.send(id_name_msg.encode('utf-8'))  # 发送消息
        tcpCliSock.close()

def spooling(myPCB, queue):
    # 根据PCB恢复断点现场
    status = myPCB.status

    if status == 0:
        tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 创建socket对象
        tcpCliSock.connect(ADDR)
        if queue.empty():  # 如果没有请求块
            tcpCliSock.send('3_spooling_WAITING'.encode('utf-8'))  # 发送消息
        else:
            req = queue.get()
            filename = req.filename
            f = open('tmp//' + filename + '.txt')
            data = f.read()
            '''print('spooling输出的信息：' + data)'''
            f.close()
            os.remove('tmp//' + filename + '.txt')
            tcpCliSock.send(('3_spooling_PRINTED_'+data).encode('utf-8'))  # 发送已经打印一个请求块消息
        tcpCliSock.close()

def kernel():  # 模拟进程调度和服务程序
    PCB = []  # 主进程与子进程共享这个PCB列表
    #初始化PCB
    pcb0 = pcbobj()
    pcb0.id = 0
    pcb0.name = 'kernel'
    PCB.append(pcb0)

    pcb1 = pcbobj()
    pcb1.id = 1
    pcb1.name = 'userA'
    PCB.append(pcb1)

    pcb2 = pcbobj()
    pcb2.id = 2
    pcb2.name = 'userB'
    PCB.append(pcb2)

    pcb3 = pcbobj()
    pcb3.id = 3
    pcb3.name = 'spooling'
    PCB.append(pcb3)

    queue = Queue(maxReqBlockNum)  # 生成一个请求块队列
    t = threading.Thread(target=serverThreading, args=(PCB,queue))  # 启动server线程,线程与父进程内存空间是共享的,可以直接修改PCB
    t.start()
    print('服务程序已启动')

    wait=2/speed

    #根据进程状态进行调度
    while True:
        rand = random.randint(0, 100)
        if rand < 45:
            if PCB[1].status!=4:
                if PCB[1].status!=0:
                    print('userA进程处于等待状态...status= ' + str(PCB[1].status))
                else:
                    print('调度了userA进程')
                    p1 = multiprocessing.Process(target=userA, args=(PCB[1],))  # 将该进程的PCB交给进程处理
                    p1.start()
                    p1.join()  # join()方法是让主进程先等待，再继续
                time.sleep(wait)
            else:
                continue
        elif rand < 90:
            if PCB[2].status != 4:
                if PCB[2].status != 0:
                    print('userB进程处于等待状态...status= ' + str(PCB[2].status))
                else:
                    print('调度了userB进程')
                    p2 = multiprocessing.Process(target=userB, args=(PCB[2],))
                    p2.start()
                    p2.join()
                time.sleep(wait)
            else:
                continue
        elif rand < 100:
            if PCB[3].status!=4:
                if PCB[3].status!=0:
                    print('spooling进程处于等待状态...status= ' + str(PCB[3].status))
                else:
                    print('调度了spooling进程')
                    p3 = multiprocessing.Process(target=spooling, args=(PCB[3], queue))
                    p3.start()
                    p3.join()
                time.sleep(wait)
            else:
                print('                                                                                                                                            spooling输出完毕！')
                break
        else:
            continue

class Ui_spoolingui(object):
    def setupUi(self, spoolingui):
        spoolingui.setObjectName("spoolingui")
        spoolingui.resize(1600, 870)
        self.pushButton = QtWidgets.QPushButton(spoolingui)
        self.pushButton.setGeometry(QtCore.QRect(1020, 10, 161, 61))
        self.pushButton.setObjectName("pushButton")
        self.lineEdit_1 = QtWidgets.QLineEdit(spoolingui)
        self.lineEdit_1.setGeometry(QtCore.QRect(520, 30, 131, 31))
        self.lineEdit_1.setObjectName("lineEdit_1")
        self.lineEdit_2 = QtWidgets.QLineEdit(spoolingui)
        self.lineEdit_2.setGeometry(QtCore.QRect(670, 30, 151, 31))
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.label_10 = QtWidgets.QLabel(spoolingui)
        self.label_10.setGeometry(QtCore.QRect(520, 10, 121, 16))
        self.label_10.setObjectName("label_10")
        self.label_20 = QtWidgets.QLabel(spoolingui)
        self.label_20.setGeometry(QtCore.QRect(670, 10, 121, 16))
        self.label_20.setObjectName("label_20")
        self.process = QtWidgets.QTextEdit(spoolingui)
        self.process.ensureCursorVisible()
        self.process.setGeometry(QtCore.QRect(10, 110, 1581, 751))
        self.process.setObjectName("textBrowser_2")
        self.label_1 = QtWidgets.QLabel(spoolingui)
        self.label_1.setGeometry(QtCore.QRect(400, 80, 121, 16))
        self.label_1.setObjectName("label_1")
        self.label_2 = QtWidgets.QLabel(spoolingui)
        self.label_2.setGeometry(QtCore.QRect(480, 80, 121, 16))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(spoolingui)
        self.label_3.setGeometry(QtCore.QRect(840, 80, 121, 16))
        self.label_3.setObjectName("label_3")
        self.label_0 = QtWidgets.QLabel(spoolingui)
        self.label_0.setGeometry(QtCore.QRect(30, 80, 141, 21))
        self.label_0.setObjectName("label_0")
        self.lineEdit_3 = QtWidgets.QLineEdit(spoolingui)
        self.lineEdit_3.setGeometry(QtCore.QRect(90, 30, 61, 31))
        self.lineEdit_3.setObjectName("lineEdit_3")
        self.label_11 = QtWidgets.QLabel(spoolingui)
        self.label_11.setGeometry(QtCore.QRect(30, 40, 61, 16))
        self.label_11.setObjectName("label_11")
        self.label_12 = QtWidgets.QLabel(spoolingui)
        self.label_12.setGeometry(QtCore.QRect(170, 40, 61, 16))
        self.label_12.setObjectName("label_12")
        self.lineEdit_4 = QtWidgets.QLineEdit(spoolingui)
        self.lineEdit_4.setGeometry(QtCore.QRect(230, 30, 61, 31))
        self.lineEdit_4.setObjectName("lineEdit_4")
        self.label_13 = QtWidgets.QLabel(spoolingui)
        self.label_13.setGeometry(QtCore.QRect(310, 40, 61, 16))
        self.label_13.setObjectName("label_13")
        self.lineEdit_5 = QtWidgets.QLineEdit(spoolingui)
        self.lineEdit_5.setGeometry(QtCore.QRect(370, 30, 61, 31))
        self.lineEdit_5.setObjectName("lineEdit_5")
        self.retranslateUi(spoolingui)
        QtCore.QMetaObject.connectSlotsByName(spoolingui)

    def retranslateUi(self, spoolingui):
        _translate = QtCore.QCoreApplication.translate
        spoolingui.setWindowTitle(_translate("spoolingui", "Form"))
        self.pushButton.setText(_translate("spoolingui", "开始打印"))
        self.label_10.setText(_translate("spoolingui", "userA打印文件数："))
        self.label_20.setText(_translate("spoolingui", "userB打印文件数："))
        self.label_1.setText(_translate("spoolingui", "userA进程"))
        self.label_2.setText(_translate("spoolingui", "userB进程"))
        self.label_3.setText(_translate("spoolingui", "spooling输出进程"))
        self.label_0.setText(_translate("spoolingui", "服务主程序："))
        self.label_11.setText(_translate("spoolingui", "调度速度："))
        self.label_12.setText(_translate("spoolingui", "井空间容量"))
        self.label_13.setText(_translate("spoolingui", "请求块个数"))

class mydialog(QDialog, Ui_spoolingui):
    def __init__(self, parent=None):
        super(mydialog, self).__init__(parent)
        self.setupUi(self)

        self.th = MyThread()
        self.th.signalForText.connect(self.onUpdateText)


        sys.stdout = self.th  # 把print()重定向到线程
        self.lineEdit_3.setText("10")
        self.lineEdit_4.setText("2000")
        self.lineEdit_5.setText("50")
        self.lineEdit_1.setText("2")
        self.lineEdit_2.setText("3")
        self.pushButton.clicked.connect(self.outText)

    def onUpdateText(self, text):
        cursor = self.process.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.process.setTextCursor(cursor)
        self.process.ensureCursorVisible()

    def outText(self):
        global maxPoolLength
        global maxReqBlockNum
        global speed
        global need
        need[1] = int(self.lineEdit_1.text())
        need[2] = int(self.lineEdit_2.text())
        need[3] = need[1]+need[2]
        speed = int(self.lineEdit_3.text())
        maxPoolLength = int(self.lineEdit_4.text())
        maxReqBlockNum = int(self.lineEdit_5.text())

        print('Running...')
        self.kernelrun()

    def kernelrun(self):
        try:
            self.t = MyThread()
            self.t.start()
        except Exception as e:
            raise e


class MyThread(QThread):
    signalForText = pyqtSignal(str)
    def __init__(self,data=None, parent=None):
        super(MyThread, self).__init__(parent)
        self.data = data

    def write(self, text):
        self.signalForText.emit(str(text))  # 发射信号
    def run(self):
        kernel()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWin = mydialog()
    myWin.show()
    sys.exit(app.exec_())